package sampledb

import sweet.mvc.api._
import sweet.helper.mail._
import sweet.helper._
import StringHelper._
import sweet.helper.jdbc._
import javax.sql.DataSource

case class User{
  var id: Int = _
  var email: String = _
  var password: String = _  
  var active: Boolean = _ 
  var createdDate: java.util.Date = _ 
  var lastModifiedDate: java.util.Date = _
  var profile: Profile = _
}

case class Profile{
  var id: Int = _
  var userId: Int = _
  var firstName: String = _
  var lastName: String = _  
  var middleName: String = _  
  var addressLine1: String = _  
  var addressLine2: String = _  
  var city: String = _          
  var state: String = _  
  var zipcode: String = _  
  var favoriteProgrammingLang: String = _
  var favoriteBrowser: String = _ 
  var favoriteOS: String = _ 
  var favoriteDatabase: String = _        
}

class UserDao(val dataSource: DataSource) extends Logger with JdbcSupport{
  
  def userCount = queryInt("select count(*) from user")
  
  def updateContacts(userId: Int, fn: String, ln: String, mn: String, 
    addr1: String, addr2: String, city: String, state: String, zipcode: String){
    logger.debug("Updating contact for userId " + userId)
    update("""update profile set 
      first_name=?, last_name=?, middle_name=?,
      address_line1=?, address_line2=?, city=?, state=?, zipcode=?
      where user_id=?""", 
      fn, ln, mn, addr1, addr2, city, state, zipcode, userId)
  }  
  def updateFavorites(userId: Int, lang: String, os: String, browser: String, db: String){
    logger.debug("Updating favorites for userId " + userId)
    update("update profile set favorite_programming_lang=?, favorite_os=?, favorite_browser=?, favorite_database=? where user_id=?", 
      lang, os, browser, db, userId)
  }  
  def updatePassword(userId: Int, newPwd: String){
    logger.debug("Changing userId "+userId+" with new password to " + newPwd)
    update("update user set password = ? where id = ?", newPwd, userId)
  }
  def save(user: User): Int = {
    logger.debug("Saving new user" +user)
    withTxJdbc{ tx =>
      tx.update("insert into user(email, password, created_date) values(?,?,now())", 
        user.email, user.password)
      user.id = tx.queryInt("select id from user where email=?", user.email)
      tx.update("insert into profile(user_id) values(?)", user.id)
    }
    user.id
  }
  def exists(email: String): Boolean = {
    logger.debug("Checkign email " +email + " exists.")
    queryBoolean("select count(*)=0 from user where email=?", email)
  }
  def authenticate(email: String, password: String): Boolean = {
    logger.debug("Authenticating email " +email + " with password.")
    queryBoolean("select count(*)=1 from user where email=? and password=?", email, password)
  }
  def get(email: String): User = {
    val user = new User
    query("select * from user u, profile p where u.id=p.user_id and email=?", email){ rsh =>
      val rs = rsh.rs
      
      user.id = rs.getInt("u.id")
      user.password = rs.getString("u.password")
      user.active = rs.getBoolean("u.active")
      user.createdDate = rs.getTimestamp("u.created_date")
      user.lastModifiedDate = rs.getTimestamp("u.last_modified_date")
      user.email = email
      
      val profile = new Profile
      profile.id = rs.getInt("p.id")
      profile.userId = rs.getInt("u.id")      
      profile.firstName = rs.getString("p.first_name")
      profile.lastName = rs.getString("p.last_name")  
      profile.middleName = rs.getString("p.middle_name")  
      profile.addressLine1 = rs.getString("p.address_line1")  
      profile.addressLine2 = rs.getString("p.address_line2")  
      profile.city = rs.getString("p.city")          
      profile.state = rs.getString("p.state")  
      profile.zipcode = rs.getString("p.zipcode")  
      profile.favoriteProgrammingLang = rs.getString("p.favorite_programming_lang")
      profile.favoriteBrowser = rs.getString("p.favorite_browser") 
      profile.favoriteOS = rs.getString("p.favorite_os") 
      profile.favoriteDatabase = rs.getString("p.favorite_database")        
      
      user.profile = profile
    }
    user
  }
}

class UserLoginInterceptor extends BeforeInterceptor{
  def beforeReq(req: Req): Option[View] = {    
    req.sess.attr("user") match {
      case None => Some(View("forward:/login-form"))
      case Some(user) => None
    }
  }
}

class UserController(emailSender: EmailSender, userDao: UserDao, loginUrl: String, adminEmail: String, siteUrl: String) extends FormController {
  handle("/index"){ View("redirect:/login-form") }
  
  def generatePassword = Rand.randAlphaNum(6)
  
  val newUserForm = new Form("newuser"){
    addField("email", "Your email address", StringConverter){
      VRequired() ++ VLen(5, 128, "Length must between 5 to 128 characters") ++
      VEmail("Email must be in <name>@<domain> format.") ++
      VValue("Email already exists."){ v => userDao.exists(v) }
    }
  }  
  handleForm("/new-form", newUserForm){ 
    new FormHandler{
      def viewForm(formData: FormData, req: Req) = View("/new-form")
      def processForm(formData: FormData, req: Req) = {
        val user = new User
        user.email = formData.singleValue("email")
        user.password = generatePassword
        
        userDao.save(user)
        logger.debug("New user created with id "+user.id)
        
        //send user the new password.
        emailSender.send(user.email, "New Registration", 
          """Welcome %s!
          |
          |Thank you for registering with SweetScala sample-db.
          |Here is the password for your account: %s
          |
          |You may now login to update your profile at:
          |%s
          |
          |Sincerely,
          |SweetScala Team.
          |http://code.google.com/p/sweetscala""".stripMargin.
          fmt(user.email, user.password, loginUrl)
        )
        
        //send admin the notification.
        val hostName = java.net.InetAddress.getLocalHost.getHostName
        emailSender.send(adminEmail, "New Registration Notification",
          """A new user has just joined site: %s
          | Email: %s
          | Date: %s
          | Host: %s
          |
          |There are total %d users registered now.
          """.stripMargin.
          fmt(siteUrl, user.email, new java.util.Date().toString, hostName, userDao.userCount)
        )
        
        View("/new-form-complete")  
      }
    }
  }
  
  val loginForm = new Form("login"){
    addField("email", "Your email address", StringConverter){
      VRequired() ++ VLen(5, 128, "Length must between 5 to 128 characters") ++
      VEmail("Email must be in <name>@<domain> format.") ++
      VValue("Email doesn't exist."){ v=> !userDao.exists(v) }
    }
    addField("password", "Your password", StringConverter){
      VRequired() ++ VLen(3, 32, "Length must between 3 to 32 characters")++
      VFunc{ (v,f,fd)=> 
        if(!userDao.authenticate(fd("email"), v))
          { fd.addError("password", "Password not match."); false }
        else
          true
      }
    }
  }
  handleForm("/login-form", loginForm){
    new FormHandler{
      def viewForm(formData: FormData, req: Req) = View("/login-form")
      def processForm(formData: FormData, req: Req) = {
        val user = userDao.get(formData("email"))
        logger.debug("Saving user data into sess")
        req.sess("user") = user
        View("redirect:/profile")
      }
    }
  }  
  handleReq("/profile"){ req =>
    req.sess.attr("user") match {
      case None => View("forward:/login-form")
      case Some(user) => View("/profile", "user"->userDao.get(user.asInstanceOf[User].email))
    }
  }  
  handleReq("/logout"){ req =>
    logger.debug("Logout user")
    val userOpt = req.sess.remove("user")
    if(userOpt != None)
      logger.debug("Logout user "+userOpt.get.asInstanceOf[User].email)
    View("/logout")
  }
  
  val pwdForm = new Form("pwdForm"){
    addField("password", "New Password", StringConverter){
      VRequired() ++ VLen(3, 32, "Length must between 3 to 32 characters")
    }
    addField("cpassword", "Confirm New password", StringConverter){
      VRequired() ++ VLen(3, 32, "Length must between 3 to 32 characters") ++
      VFunc{ (fieldValue, fieldName, formData) => 
        if(fieldValue != formData("password")){
          formData.addError(fieldName, "Confirmation password not match.")
          false
        }else{ true }
      }
    }
  }
  handleForm("/chgpwd-form", pwdForm){
    new FormHandler{
      def viewForm(formData: FormData, req: Req) = View("/chgpwd-form")
      def processForm(formData: FormData, req: Req) = {
        val user = req.sess("user").asInstanceOf[User]
        user.password = formData("password")
        userDao.updatePassword(user.id, user.password)
        emailSender.send(user.email, "Profile Updates", 
          """Hello %s,
          |
          |You have successfully changed your password to: %s
          |
          |Sincerely,
          |SweetScala Team.
          |http://code.google.com/p/sweetscala""".stripMargin.
          fmt(user.email, user.password) 
        )
        View("redirect:/profile")
      }
    }
  }  
  
  val editFavoritesForm = new Form("editFavoritesForm"){ 
    addMultiValuesField("favoriteProgrammingLang", "What is your favavorite programming language?", 
      StringConverter, VOptional()){
      OrderedMap("None"->"", "Scala"->"scala", "Java"->"java", "PHP"->"php", "Perl"->"perl", "Ruby"->"ruby", "Python"->"python", "Other"->"other")
    }
    addMultiValuesField("favoriteOS", "What is your favavorite Operating System?", 
      StringConverter, VOptional()){
      OrderedMap("None"->"", "Mac OSX"->"macosx", "Linux"->"linux", "Windows"->"windows", "Unix"->"unix", "Other"->"other")
    }
    addMultiValuesField("favoriteBrowser", "What is your favavorite Browser?", 
      StringConverter, VOptional()){
      OrderedMap("None"->"", "Firefox"->"firefox", "Safari"->"safari", "InternetExplorer"->"ie", "Other"->"other")
    }
    addMultiValuesField("favoriteDatabase", "What is your favavorite Database?", 
      StringConverter, VOptional()){
      OrderedMap("None"->"", "MySQL"->"mysql", "Oracle"->"oracle", "MSSQLServer"->"mssql", "Other"->"other")
    }
    
    override def initialFieldValue(multiValName: String, field: Field, formData: FormData, req: Req) = {
      val user = userDao.get(req.sess("user").asInstanceOf[User].email)
      if(field.name=="favoriteProgrammingLang") user.profile.favoriteProgrammingLang
      else if(field.name=="favoriteOS") user.profile.favoriteOS
      else if(field.name=="favoriteBrowser") user.profile.favoriteBrowser
      else if(field.name=="favoriteDatabase") user.profile.favoriteDatabase
      else{ super.initialFieldValue(field, formData, req) }
    }
  }
  handleForm("/edit-favorites-form", editFavoritesForm){
    new FormHandler{
      def viewForm(formData: FormData, req: Req) = View("/edit-favorites-form")
      def processForm(formData: FormData, req: Req) = {
        val user = req.sess("user").asInstanceOf[User]
        userDao.updateFavorites(user.id, formData("favoriteProgrammingLang"), formData("favoriteOS"), 
          formData("favoriteBrowser"), formData("favoriteDatabase"))
        View("redirect:/profile")  
      }
    }
  }  
  
  val editContactForm = new Form("editContactForm"){ 
    addField("firstName", "First Name", StringConverter){
      VOptional() ++ VLen(3, 32, "Length must between 3 to 32 characters")
    }
    addField("lastName", "Last Name", StringConverter){
      VOptional() ++ VLen(3, 32, "Length must between 3 to 32 characters")
    }
    addField("middleName", "Middle Name", StringConverter){
      VOptional() ++ VLen(3, 32, "Length must between 3 to 32 characters")
    }
    addField("addressLine1", "Address Line 1", StringConverter){
      VOptional() ++ VLen(3, 63, "Length must between 3 to 63 characters")
    }
    addField("addressLine2", "Address Line 2", StringConverter){
      VOptional() ++ VLen(3, 63, "Length must between 3 to 63 characters")
    }    
    addField("city", "City", StringConverter){
      VOptional() ++ VLen(3, 32, "Length must between 3 to 32 characters")
    }
    addField("state", "State", StringConverter){
      VOptional() ++ VLen(2, 2, "Length must 2 characters")
    }
    addField("zipcode", "Zipcode", StringConverter){
      VOptional() ++ VLen(5, 10, "Length must between 3 to 32 characters")
    }
    
    def normalizeString(obj: AnyRef) = { if(obj == null) "" else obj.toString }
    
    override def initialFieldValue(field: Field, formData: FormData, req: Req) = {
      //TODO: This is not very efficient as it gets call per field! what can we do?
      val user = userDao.get(req.sess.attr[User]("user").get.email)
      val profile = user.profile
      if(field.name=="firstName") normalizeString(profile.firstName)
      else if(field.name=="lastName") normalizeString(profile.lastName)
      else if(field.name=="middleName") normalizeString(profile.middleName)
      else if(field.name=="addressLine1") normalizeString(profile.addressLine1)
      else if(field.name=="addressLine2") normalizeString(profile.addressLine2)
      else if(field.name=="city") normalizeString(profile.city)
      else if(field.name=="state") normalizeString(profile.state)
      else if(field.name=="zipcode") normalizeString(profile.zipcode)
      else{ super.initialFieldValue(field, formData, req) }
    }
  }
  handleForm("/edit-contact-form", editContactForm){
    new FormHandler{
      def viewForm(formData: FormData, req: Req) = View("/edit-contact-form")
      def processForm(formData: FormData, req: Req) = {
        val user = req.sess.attr[User]("user").get
        userDao.updateContacts(user.id, 
          formData("firstName"), formData("lastName"), formData("middleName"), 
          formData("addressLine1"), formData("addressLine2"), 
          formData("city"), formData("state"), formData("zipcode"))
        View("redirect:/profile")  
      }
    }
  }  
}
